package com.onluy.framework.system.interceptor;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.onluy.framework.data.util.InParam;
import com.onluy.framework.tools.ReflectHelper; 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List; 
import java.util.Properties;

@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
public class EasyuiPageInterceptor implements Interceptor{

	 protected Logger log = LoggerFactory.getLogger(this.getClass()); 
	
	 // 需要进行分页操作的字符串正则表达式
	 private String pattern = "^.*ByPage$";   
	 /**  数据库方言：目前只支持mysql、oracle、sqlServer；默认mysql */
	 private String dialect = "mysql";
	 

    public String getPattern() {
		return pattern;
	}

	public void setPattern(String pattern) {
		this.pattern = pattern;
	}

	public String getDialect() {
		return dialect;
	}

	public void setDialect(String dialect) {
		this.dialect = dialect;
	}

	public Object intercept(Invocation invocation) throws Throwable {
        //对于StatementHandler其实只有两个实现类，一个是RoutingStatementHandler，另一个是抽象类BaseStatementHandler，  
        //BaseStatementHandler有三个子类，分别是SimpleStatementHandler，PreparedStatementHandler和CallableStatementHandler，  
        //SimpleStatementHandler是用于处理Statement的，PreparedStatementHandler是处理PreparedStatement的，而CallableStatementHandler是  
        //处理CallableStatement的。Mybatis在进行Sql语句处理的时候都是建立的RoutingStatementHandler，而在RoutingStatementHandler里面拥有一个  
        //StatementHandler类型的delegate属性，RoutingStatementHandler会依据Statement的不同建立对应的BaseStatementHandler，即SimpleStatementHandler、  
        //PreparedStatementHandler或CallableStatementHandler，在RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的delegate对应的方法。  
        //我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截StatementHandler接口的prepare方法，又因为Mybatis只有在建立RoutingStatementHandler的时候  
        //是通过Interceptor的plugin方法进行包裹的，所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。
		 StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
		    MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
		        SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
		    
		    MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

		    
		String id = mappedStatement.getId();
		log.info("===============intercept.id:"+id); 
	    //匹配在mybatis中定义的与分页有关的查询id 
		if(invocation.getTarget() instanceof RoutingStatementHandler && id.matches(pattern)){   
            StatementHandler delegate = (StatementHandler) ReflectHelper.getFieldValue(statementHandler, "delegate");  
            BoundSql boundSql = delegate.getBoundSql();
            Object obj = boundSql.getParameterObject();
            if (obj instanceof InParam) {  
            	InParam inParam = (InParam) obj;  
                 //拦截到的prepare方法参数是一个Connection对象  
                Connection connection = (Connection)invocation.getArgs()[0];  
                //获取当前要执行的Sql语句，也就是我们直接在Mapper映射语句中写的Sql语句  
                String sql = boundSql.getSql();  
                //给当前的page参数对象设置总记录数  
                this.setTotalRecord(inParam, mappedStatement, connection);  
                //获取分页Sql语句  
                String pageSql = this.getPageSql(inParam, sql);  
                //利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句  
    			log.info("===============intercept.pageSql:"+pageSql); 
                ReflectHelper.setFieldValue(boundSql, "sql", pageSql);  
            } 
        }  
        return invocation.proceed();  
    }
    
    /** 
     * 给当前的参数对象page设置总记录数 
     * 
     * @param page Mapper映射语句对应的参数对象 
     * @param mappedStatement Mapper映射语句 
     * @param connection 当前的数据库连接 
     */  
    private void setTotalRecord(InParam  inParam,  
           MappedStatement mappedStatement, Connection connection) {  
       //获取对应的BoundSql，这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。  
       //delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。  
       BoundSql boundSql = mappedStatement.getBoundSql(inParam);  
       //获取到我们自己写在Mapper映射语句中对应的Sql语句  
       String sql = boundSql.getSql();  
       //通过查询Sql语句获取到对应的计算总记录数的sql语句  
       String countSql = this.getCountSql(sql);  
       //通过BoundSql获取对应的参数映射  
       List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
       //利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。  
       BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, inParam);  
       //通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象  
       ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, inParam, countBoundSql);  
       //通过connection建立一个countSql对应的PreparedStatement对象。  
       PreparedStatement pstmt = null;  
       ResultSet rs = null;  
       try {  
           pstmt = connection.prepareStatement(countSql);  
           //通过parameterHandler给PreparedStatement对象设置参数  
           parameterHandler.setParameters(pstmt);  
           //之后就是执行获取总记录数的Sql语句和获取结果了。  
           rs = pstmt.executeQuery();  
           if (rs.next()) {  
              int totalRecord = rs.getInt(1);  
              //给当前的参数page对象设置总记录数  
              inParam.put("total", totalRecord );  
           }  
       } catch (SQLException e) {  
           e.printStackTrace();  
       } finally {  
           try {  
              if (rs != null)  
                  rs.close();  
               if (pstmt != null)  
                  pstmt.close();  
           } catch (SQLException e) {  
              e.printStackTrace();  
           }  
       }  
    }  
    
    /** 
     * 根据原Sql语句获取对应的查询总记录数的Sql语句 
     * @param sql 
     * @return 
     */  
    private String getCountSql(String sql) {   
    	String mksql="select count(1) as totle from (" + sql + ") aaa123";
		log.info("===============getCountSql:"+mksql); 
       return  mksql;
    }  
    
    /** 
     * 根据page对象获取对应的分页查询Sql语句，这里只做了两种数据库类型，Mysql和Oracle 
     * 其它的数据库都 没有进行分页 
     * 
     * @param page 分页对象 
     * @param sql 原sql语句 
     * @return 
     */  
    private String getPageSql(InParam page, String sql) {  
       StringBuffer sqlBuffer = new StringBuffer(sql);  
       if ("mysql".equalsIgnoreCase(dialect)) {  
           return getMysqlPageSql(page, sqlBuffer);  
       } else if ("oracle".equalsIgnoreCase(dialect)) {  
           return getOraclePageSql(page, sqlBuffer);  
       }  
       return sqlBuffer.toString();  
    }  
    
    /** 
    * 获取Mysql数据库的分页查询语句 
    * @param page 分页对象 
    * @param sqlBuffer 包含原sql语句的StringBuffer对象 
    * @return Mysql数据库分页语句 
    */  
   private String getMysqlPageSql(InParam inParam, StringBuffer sqlBuffer) {  
       int page = inParam.getInt("page", 1);
       int pagerows = inParam.getInt("rows", 10);
       page = (page <= 0 ? 1 : page);
       pagerows = (pagerows <= 0 ? 10 : pagerows); 
       sqlBuffer.append("  limit   "+((page - 1) * pagerows + (page > 1 ? 1 : 0))+" ,  "+(page * pagerows));
      return sqlBuffer.toString();  
   }  
    
   /** 
    * 获取Oracle数据库的分页查询语句 
    * @param page 分页对象 
    * @param sqlBuffer 包含原sql语句的StringBuffer对象 
    * @return Oracle数据库的分页查询语句 
    */  
   private String getOraclePageSql(InParam page, StringBuffer sqlBuffer) {  
   /*   //计算第一条记录的位置，Oracle分页是通过rownum进行的，而rownum是从1开始的  
      int offset = (page.getPage() - 1) * page.getRows() + 1;  
      sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getRows());  
      sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);  
      //上面的Sql语句拼接之后大概是这个样子：  
      //select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16  
*/      return sqlBuffer.toString();  
   }  
   
      
    /** 
     * 拦截器对应的封装原始对象的方法 
     */        
    public Object plugin(Object arg0) {  
        // TODO Auto-generated method stub  
        if (arg0 instanceof StatementHandler) {  
            return Plugin.wrap(arg0, this);  
        } else {  
            return arg0;  
        } 
    }  

    //    在配置插件的时候配置默认参数
    @Override
    public void setProperties(Properties properties) {
      
    }

    public static String TransactSQL(String str)
    {
          return str.replaceAll(".*([';]+|(--)+).*", " "); 
    }
  
    
}
